<?php


namespace app\utils;


use app\ErrorCode;
use app\lib\exception\AuthException;
use DateTimeImmutable;
use DateTimeZone;
use Lcobucci\Clock\SystemClock;
use Lcobucci\JWT\Configuration;
use Lcobucci\JWT\Signer\Hmac\Sha256;
use Lcobucci\JWT\Signer\Key\InMemory;

use Lcobucci\JWT\Validation\Constraint\IssuedBy;
use Lcobucci\JWT\Validation\Constraint\PermittedFor;
use Lcobucci\JWT\Validation\Constraint\LooseValidAt;
use Lcobucci\JWT\Validation\Constraint\IdentifiedBy;
use Lcobucci\JWT\Validation\Constraint\RelatedTo;
use Lcobucci\JWT\Validation\Constraint\SignedWith;

use think\Exception;
use think\facade\Config;
use think\facade\Cache;

class JWT
{
    /**
     * 得到配置对象
     * @return Configuration
     */
    private static function getConfig()
    {
        return Configuration::forSymmetricSigner(new Sha256(), InMemory::plainText(Config::get('jwt.jti')));
    }


    /**
     * @param array|string|integer $data 往token里面传入的数据
     * @return string|boolean 执行成功返回token字符串 ,失败返回false
     */
    public static function create($data)
    {
        try {
            $config = self::getConfig();
            $now = new DateTimeImmutable('now', new DateTimeZone(Config::get('jwt.timezone')));
            return $config->builder()
                //主题
                ->relatedTo(Config::get('jwt.sub'))
                //签发人
                ->issuedBy(Config::get('jwt.iss'))
                //接收人  // canOnlyBeUsedBy方法在4.x中将会被移除被permittedFor替代
                ->permittedFor(...array_filter(Config::get('jwt.aud', []), 'is_string'))
                //唯一标志
                ->identifiedBy(Config::get('jwt.jti'))
                //签发时间
                ->issuedAt($now)
                //生效时间（立即生效:签发时间减一秒）
                ->canOnlyBeUsedAfter($now->modify('-1 second'))
                //过期时间
                ->expiresAt($now->modify("+" . Config::get('jwt.exp') . " second"))
                //存在token中的数据   // with方法在4.x中将会被移除被withClaim替代
                    ->withClaim('_thinkJwt', json_encode($data, JSON_UNESCAPED_SLASHES + JSON_UNESCAPED_UNICODE))
                //签名
                ->getToken($config->signer(), $config->signingKey())
                ->toString();
        } catch (\Exception $e) {
            throw $e;
        }

    }


    /**
     * 解析token
     * @param $token
     * @return mixed
     */
    public static function parse($token)
    {
        $config = self::getConfig();

        //注销token逻辑
        $delete_token = self::getDeleteToken();
        if (in_array($token, $delete_token))
        {
            //token已被注销
            throw new Exception('该token已经注销');
        }

        //token解析异常必须要用try catch抓取 当JWT不是字符串或无效时会抛出异常
        try {
            $token = $config->parser()->parse($token);
        } catch (\Exception $e) {
            throw new Exception('token解码失败');
        }

        //验证声明iss是否列为预期值
        $issued = new IssuedBy(Config::get('jwt.iss'));
        if (!$config->validator()->validate($token, $issued))
        {
            throw new Exception('token签发人验证失败');
        }

        $auds = array_filter(Config::get('jwt.aud', []), 'is_string');
        foreach ($auds as $aud) {
            if (!$config->validator()->validate($token, new PermittedFor($aud)))
            {
                throw new Exception('token接收人验证失败');
            }
        }

        //验证声明是否jti与预期值匹配
        $jti = new IdentifiedBy(Config::get('jwt.jti'));
        if (!$config->validator()->validate($token, $jti))
        {
            throw new Exception('token编号验证失败');
        }

        //验证声明是否sub与预期值匹配
        $sub = new RelatedTo(Config::get('jwt.sub'));
        if (!$config->validator()->validate($token, $sub))
        {
            throw new Exception('token主题验证失败');
        }

        //验证令牌是否使用预期的签名者和密钥签名
        $sign = new SignedWith($config->signer(), $config->signingKey());
        if (!$config->validator()->validate($token, $sign))
        {
            throw new Exception('token签名密钥验证失败');
        }

        //从token中取出存储的数据
        $data = json_decode($token->claims()->get('_thinkJwt'), true);
        return $data;

    }


    /**
     * @param string $token 需要注销的token
     * @return void
     */
    public static function logout(string $token)
    {

        //取缓存中注销的token数组
        $delete_token = self::getDeleteToken();
        //把传递过来的token再存入缓存
        $delete_token[] = $token;

        //再次把新的缓存数据重新存入缓存中，缓存时间必须大于等于jwt生成时的有效期,否则注销不成功
        Cache::set(Config::get('jwt.delete_key'), $delete_token, Config::get('jwt.exp'));
    }


    /**
     * 获取请求头传递过来的HTTP_AUTHORIZATION字段的值,成功返回token字符串失败返回false
     * @return false|string
     */
    public static function getRequestToken()
    {
        if (empty($_SERVER['HTTP_AUTHORIZATION']))
        {
            return false;
        }
        $header = $_SERVER['HTTP_AUTHORIZATION'];
        $method = 'bearer';
        //去除token中可能存在的bearer标识
        return trim(str_ireplace($method, '', $header));
    }


    /**
     * 从缓存中拿到已经被注销的token
     * @return array|mixed
     */
    private static function getDeleteToken()
    {
        return Cache::get(Config::get('jwt.delete_key')) ?: [];
    }

}